function createElement(type,props,...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => 
        typeof child === 'object'? child : createTextElement(child)
      )
    }
  }
}

function createTextElement(text) {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: text,
      children: []
    }
  }
}

// 根据fiber节点去创建dom节点
function createDom(fiber) {
  const dom = fiber.type === 'TEXT_ELEMENT' ? document.createTextNode('') : document.createElement(fiber.type)

  updateDom(dom, {}, fiber.props)

  return dom
}

const isEvent = key => key.startsWith('on') //监听事件的属性都是以on开头
const isProperty = key => key !== 'children' && !isEvent(key)
const isNew = (prev, next) => key => prev[key] !== next[key] //判断是否是新属性
const isGone = (prev, next) => key => !(key in next) //判断是否是旧属性

function updateDom(dom, prevProps, nextProps) {
  // 移除旧的或者是改变事件监听
  Object.keys(prevProps).filter(isEvent).filter(key => !(key in nextProps) || isNew(prevProps, nextProps)(key))
  .forEach(name => {
    const eventType = name.toLowerCase().substring(2)
    dom.removeEventListener(eventType, prevProps[name])
  })

  // 移除旧的属性
  Object.keys(prevProps).filter(isProperty).filter(isGone(prevProps, nextProps))
  .forEach(name => {
    dom[name] = ''
  })

  // 添加新的属性或者是更新属性
  Object.keys(nextProps).filter(isProperty).filter(isNew(prevProps, nextProps))
  .forEach(name => {
    dom[name] = nextProps[name]
  })

  // 添加事件监听
  Object.keys(nextProps).filter(isEvent).filter(isNew(prevProps, nextProps))
  .forEach(name => {
    const eventType = name.toLowerCase().substring(2)
    dom.addEventListener(eventType, nextProps[name])
  })
}

// 提交fiber树对dom节点进行更新，删除的节点因为已经不存在当前的fiber树上面，所以需要用deletions存储并且单独遍历
function commitRoot() {
  deletions.forEach(commitWork)
  commitWork(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
}

// 递归遍历整棵树
function commitWork(fiber) {
  if (!fiber) {
    return
  }

  let domParentFiber = fiber.parent
  // 这个循环是因为如果是fiber是函数类型的话他是没有dom节点的，那么就需要往上去找dom节点
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent
  }
  const domParent = domParentFiber.dom

  if(fiber.effectTag === 'PLACEMENT' && fiber.dom !== null) {
    domParent.appendChild(fiber.dom)
  } else if (fiber.effectTag === 'DELETION') {
    commitDeletion(fiber, domParent)
  } else if (fiber.effectTag === 'UPDATE' && fiber.dom !== null) {
    updateDom(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
    )
  }

  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child, domParent)
  }
}

// 设置fiber树的根节点并且赋值给nextUniOfWork，wrokLoop函数判断nextUniOfWork不为空则开始遍历fiber树
function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    },
    alternate: currentRoot
  }
  deletions = []

  nextUniOfWork = wipRoot
}

let nextUniOfWork = null //下一个执行的任务
let currentRoot = null // 当前提交给DOM的最近一次的fiber树
let wipRoot = null // wipRoot为fiber树的根节点
let deletions = null // 记录需要删除的fiber节点

function workLoop(deadline) {
  let shouldYield = false
  // 当遍历完整个fiber树或者剩余时间不够时结束循环
  while (nextUniOfWork && !shouldYield) {
    // 处理当前工作区内容并且返回下一个工作区内容
    nextUniOfWork = performUnitOfWork(
      nextUniOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }

  // 如果没有下一个工作内容而且fiber树的根节点存在
  if(!nextUniOfWork && wipRoot) {
    commitRoot()
  }

  requestIdleCallback(workLoop)
}

// requestIdleCallback 是一个用于在浏览器空闲时执行回调函数的API,回调函数接受一个 deadline 对象作为参数。deadline 对象有一个 timeRemaining 方法，用于返回剩余的执行时间。
requestIdleCallback(workLoop)

function performUnitOfWork(fiber) {
  //判断是否是函数组件 函数组件没有dom节点
  const isFunctionComponent = fiber.type instanceof Function

  if (isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else {
    updateHostComponent(fiber)
  }
  

  // 判断是否有子节点 如果有子节点先遍历子节点 类似于深度优先遍历
  if (fiber.child) {
    return fiber.child
  }

  // 到了这里说明当前fiber已经没有了child，但是也不是最深的节点，因为兄弟节点里面可能也会有child
  // 先判断当前节点有没有兄弟节点 如果有兄弟节点就返回兄弟节点（然后遍历所有的兄弟节点），如果没有这返回父节点的兄弟节点（然后遍历所有的父节点的兄弟节点）
  let nextFiber = fiber
  while(nextFiber) {
    if(nextFiber.sibling) {
      // 这里返回的是fiber的父节点的兄弟节点，因为在上一次循环结束的时候nextFiber已经指向了fiber的父节点 注意没有直接返回父节点是因为上一个判断当前节点是否有子节点 这样会造成死循环
      return nextFiber.sibling
    }

    nextFiber = nextFiber.parent
  }
}

let wipFiber = null // 当前处理的函数fiber，当执行fiber.type的时候会调用useStatue函数，用于useState获取到当前函数fiber
let stateHookIndex = null // 钩子队列的索引，用于获取上一个hook的状态
let effectHookIndex = null

function updateFunctionComponent(fiber) {
  wipFiber = fiber
  stateHookIndex = 0
  effectHookIndex = 0
  // 存放在函数Fiber里面的钩子
  wipFiber.stateHooks = []
  wipFiber.effectHooks = []
  // fiber.type执行返回的就是函数组件里面最外层的dom元素，这样就解决了函数组件没有dom的问题
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}

function useState(initial) {
  const oldHook = 
    wipFiber.alternate &&
    wipFiber.alternate.stateHooks &&
    wipFiber.alternate.stateHooks[stateHookIndex]

  const hook ={
    // 更新的话拿上一次的值，初始化则使用传入的值
    state: oldHook? oldHook.state : initial,
    queue: [],
  }

  // 初始化的时候是没有操作的，需要调用了setState队列里面才会有具体操作(下文所说的上面的代码！！！)
  const actions = oldHook ? oldHook.queue : []
  actions.forEach(action => {
    hook.state = action(hook.state)
  })

  const setState = action => {
    // 将一些更新的操作放到队列里面，触发更新以后会在上面的代码里面执行
    hook.queue.push(action)
    // 给wipRoot赋值就会触发页面更新，调用workLoop函数
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot
    }
    nextUniOfWork = wipRoot
    deletions = []
  }

  wipFiber.stateHooks.push(hook)
  stateHookIndex++
  return [hook.state, setState]
}

function useEffect(callback, dependencies) {
  const oldHook = 
    wipFiber.alternate &&
    wipFiber.alternate.effectHooks &&
    wipFiber.alternate.effectHooks[effectHookIndex]
  
  const hook = {
    callback,
    dependencies,
  }

  if(oldHook) {
    // 判断依赖是否发生变化，如果发生变化则执行回调函数
    if(dependencies.some((dep, index) => dep !== oldHook.dependencies[index])) {
      // 判断是否有清除副作用的函数 有的话优先执行
      if (oldHook.cleanup) {
        oldHook.cleanup()
      }
      hook.cleanup = oldHook.callback()
    }
  } else {
    // 判断是否第一次执行，并且传入依赖值为空
    if(!wipFiber.alternate && dependencies.length === 0) {
      hook.cleanup = callback()
    }
  }

  wipFiber.effectHooks.push(hook)
  effectHookIndex++
}

function updateHostComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }

  const elements = fiber.props.children
  reconcileChildren(fiber, elements)
}

// 遍历children为子节点创建fiber
function reconcileChildren(wipFiber, elements) {
  let index = 0
  let olderFiber = wipFiber.alternate && wipFiber.alternate.child
  let prevSibling = null

  while (index < elements.length || olderFiber != null) {
    const element = elements[index]
    let newFiber = null

    const sameType = olderFiber && element && element.type === olderFiber.type

    if (sameType) {
      newFiber = {
        type: olderFiber.type,
        props: element.props,
        dom: olderFiber.dom,
        parent: wipFiber,
        alternate: olderFiber,
        effectTag: 'UPDATE'
      }
    }
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: 'PLACEMENT'
      }
    }
    if (olderFiber && !sameType) {
      olderFiber.effectTag = 'DELETION'
      deletions.push(olderFiber)
    }

    if (olderFiber) {
      olderFiber = olderFiber.sibling
    }

    // fiber的父节点只有一个child指向子节点的第一个元素 后续的子节点不能通过父节点直接访问 都是以单向链表的形式通过child去访问
    // fiber
    // ↓child
    // element[0] --sibling--> element[1] --sibling--> element[2]
    // 子节点都有一个parent指向父节点
    if(index === 0) {
      wipFiber.child = newFiber
    } else if (element) {
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
  }
}


const Didact = {
  createElement,
  render,
  useState,
  useEffect
}

/** @jsx Didact.createElement */
function Counter() {
  const [state, setState] = Didact.useState(1)

  Didact.useEffect(() => {
    window.alert('你点击了'+ state +'次')

    const timer = setTimeout(() => {
      console.log(state, '123123');
    }, 2000)

    return () => {
      clearTimeout(timer)
    }
  }, [state])
  return (
    <div>
      <h1 onClick={() => setState(c => c + 1)}>
        Count: {state}
      </h1>
      <h3>
        Clicked: {state} times
      </h3>
    </div>
  )
}
const element = <Counter />

const container = document.getElementById('root')
Didact.render(element, container)